/*
Copyright (c) 2012 VBChunguk

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

#include "DanmakuCompiler.hpp"
#include <Shlwapi.h>
#pragma comment(lib, "shlwapi")

namespace Danmaku
{
	bool DanmakuCompiler::OpenFile(string path)
	{
		try {
			SourceCode.exceptions(ios_base::failbit|ios_base::badbit);
			SourceCode.open(path, ios_base::in);

			DanmakuCompilerEntry CurrentEntry;
			uint32_t position;
			position = 0;
			CurrentEntry.name = "";
			CurrentEntry.pos = 0;
			nowpos = varnowpos = 0;
			LableList.clear();
			while(1) {
				char *buf;
				string line;
				buf = new char[1025];
				SourceCode.exceptions(0);
				SourceCode.getline(buf, 1024);
				if(SourceCode.eof()) break;
				if(SourceCode.fail() || SourceCode.bad()) {
					return false;
				}
				SourceCode.exceptions(ios_base::failbit|ios_base::badbit);
				line = buf;
				delete buf;

				strTrim(line);
				vector<string> splitted;
				splitted = StrSplit(line, " ");
				toLower(splitted[0]);
				if(splitted[0] == "entry") { // 코드 엔트리 선언
					vector<DanmakuCompilerEntry>::iterator i;
					string tempEntry(splitted[1]);
					for(i = EntryList.begin(); i != EntryList.end(); i++) {
						if(i->name == tempEntry) {
							if(i->coded)
								throw DanmakuCompilerException(DanmakuCompilerException::EntryAlreadyExists);
							else break;
						}
					}
					if(i == EntryList.end()) {
						CurrentEntry.name = tempEntry;
						CurrentEntry.pos = nowpos;
						CurrentEntry.coded = true;
						EntryList.push_back(CurrentEntry);
					} else {
						i->coded = true;
						i->pos = nowpos;
						CurrentEntry = *i;
					}
				} else if(splitted[0] == "move") { // move 계열 명령어
					vector<string> spl2;
					int lhstype, rhstype;

					toLower(splitted[1]);
					spl2 = StrSplit(splitted[1], ",");

					if(spl2[0] == "a") lhstype = 0;
					else if(spl2[0] == "b") lhstype = 1;
					else if(spl2[0] == "x") lhstype = 2;
					else if(spl2[0] == "y") lhstype = 3;
					else if(spl2[0] == "bp") lhstype = 4;
					else if(spl2[0] == "mask") lhstype = 5;
					//else if(spl2[0].substr(0, 5) == "data ") lhstype = 6;
					else if(spl2[0].substr(0, 2) == "bp") lhstype = 7;
					else lhstype = -1;

					if(spl2[1] == "a") rhstype = 0;
					else if(spl2[1] == "sp") rhstype = 1;
					else if(spl2[1].substr(0, 2) == "bp") rhstype = 2;
					//else if(spl2[1].substr(0, 5) == "data ") rhstype = 3;
					else rhstype = -1;

					//if(lhstype == -1) throw DanmakuCompilerException(DanmakuCompilerException::InstructionArg);
					switch(lhstype) {
					case 0: // A
						{
							if(rhstype == 0 || rhstype == 1) // move a,a와 move a,sp는 없는 명령어
								throw DanmakuCompilerException(DanmakuCompilerException::InstructionArg);
							if(rhstype == -1) { // move a,(value) 또는 move a,var
								bool _res;
								uint32_t parsedValue;
								_res = ValueParse(spl2[1], parsedValue);
								if(!_res) { // 변수명일 가능성이 있음
									vector<DanmakuVariable>::iterator i;
									for(i = VarList.begin(); i != VarList.end(); i++) { // 변수 리스트에서 찾아 보자
										if(i->name == spl2[1]) break;
									}
									if(i == VarList.end()) // 변수 리스트에 없잖아?
										throw DanmakuCompilerException(DanmakuCompilerException::InstructionArg);
									// move a,var
									char *_data = new char[4];
									memcpy(_data, &(i->pos), 4);
									MakeInstruction(0x000D, 0, 4, _data);
								} else { // move a,(value)
									char *_data = new char[4];
									memcpy(_data, &parsedValue, 4);
									MakeInstruction(0x0000, 0, 4, _data);
								}
							}
						}
						break;
					case -1: // 변수?
						{
							vector<DanmakuVariable>::iterator i;
							for(i = VarList.begin(); i != VarList.end(); i++) { // 변수 리스트에서 찾아 보자
								if(i->name == spl2[0]) break;
							}
							if(i == VarList.end()) // 변수 리스트에 없잖아?
								throw DanmakuCompilerException(DanmakuCompilerException::InstructionArg);

							if(rhstype != 0) // move var,a만 유효함
								throw DanmakuCompilerException(DanmakuCompilerException::InstructionArg);

							char *_data = new char[4];
							memcpy(_data, &(i->pos), 4);
							MakeInstruction(0x000E, 0, 4, _data);
						}
						break;
					}
				} else if(splitted[0] == "push") { // 스택 push
					string splitTemp(splitted[1]);
					strTrim(splitted[1]);
					toLower(splitted[1]);
					int argtype;
					if(splitted[1] == "a") argtype = 0;
					else if(splitted[1] == "b") argtype = 1;
					else if(splitted[1] == "bp") argtype = 2;
					else {
						bool _res;
						uint32_t parsedValue;
						_res = ValueParse(splitted[1], parsedValue);
						if(!_res) {// 엔트리일까?
							vector<string> ColonSplit;
							ColonSplit = StrSplit(splitTemp, ":");
							toLower(ColonSplit[0]);
							if(ColonSplit[0] == "entry") {
								vector<DanmakuCompilerEntry>::iterator i;
								parsedValue = 0;
								for(i = EntryList.begin(); i != EntryList.end(); i++, parsedValue++) {
									if(i->name == ColonSplit[1]) break;
								}
								if(i == EntryList.end()) // 그런 엔트리는 없다
									throw DanmakuCompilerException(DanmakuCompilerException::InstructionArg);
							} else // 대응하지 않는 피연산자
								throw DanmakuCompilerException(DanmakuCompilerException::InstructionArg);
						}
						argtype = -1;
						char *_data = new char[4];
						memcpy(_data, &parsedValue, 4);
						MakeInstruction(0x000F, 0, 4, _data);
					}

					switch(argtype) {
					case 0: // push a
						MakeInstruction(0x0004, 0, 0, NULL);
						break;
					case 1: // push b
						MakeInstruction(0x0005, 0, 0, NULL);
						break;
					case 2: // push bp
						MakeInstruction(0x000C, 0, 0, NULL);
						break;
					}
				} else if(splitted[0] == "pop") { // 스택 pop
					toLower(splitted[1]);
					int argtype;
					if(splitted[1] == "a") argtype = 0;
					else if(splitted[1] == "b") argtype = 1;
					else if(splitted[1] == "bp") argtype = 2;
					else // 대응하지 않는 피연산자
						throw DanmakuCompilerException(DanmakuCompilerException::InstructionArg);

					switch(argtype) {
					case 0: // pop a
						MakeInstruction(0x0006, 0, 0, NULL);
						break;
					case 1: // pop b
						MakeInstruction(0x0007, 0, 0, NULL);
						break;
					case 2: // pop bp
						MakeInstruction(0x000B, 0, 0, NULL);
						break;
					}
				} else if(splitted[0] == "return") { // 리턴
					MakeInstruction(0x00FF, 0, 0, NULL);
				} else if(splitted[0] == "var") { // 전역 변수 선언
					vector<string> varval;
					DanmakuVariable v;
					v.scope = 0;
					varval = StrSplit(splitted[1], "=");
					strTrim(varval[0]);
					vector<string> vararg;
					vararg = StrSplit(varval[0], "[");
					if(vararg.size() == 1) {
						v.len = 4;
					} else {
						string sizestr(vararg[1].c_str(), (&vararg[1].back()));
						bool res = ValueParse(sizestr, v.len);
						if(!res) throw DanmakuCompilerException(DanmakuCompilerException::InstructionArg);
					}
					v.DefValue = new char[v.len];
					if(varval.size() == 1) {
						memset(v.DefValue, 0, v.len);
					} else {
						// TODO: 변수 값 해석
						throw DanmakuCompilerException(DanmakuCompilerException::unknown);
					}
					strTrim(vararg[0]);
					toLower(vararg[0]);
					v.name = vararg[0];
					v.pos = varnowpos;
					varnowpos += v.len;
					VarList.push_back(v);
				} else if(splitted[0] == "dcreate") { // 탄막 생성
					MakeInstruction(0x0090, 0, 0, NULL);
				} else if(splitted[0] == "dactivate") { // 탄막 활성화
					MakeInstruction(0x0094, 0, 0, NULL);
				} else if(splitted[0] == "dsetposxy") { // 탄막 위치 이동 XY
					MakeInstruction(0x0091, 0, 0, NULL);
				} else if(splitted[0] == "dsetspeed") { // 탄막 속도 변경
					MakeInstruction(0x0093, 0, 0, NULL);
				} else if(splitted[0] == "dsetdir") { // 탄막 방향 변경
					MakeInstruction(0x0092, 0, 0, NULL);
				} else if(splitted[0] == "dgetdir") { // 탄막 방향 가져오기
					MakeInstruction(0x0192, 0, 0, NULL);
				} else if(splitted[0] == "dgetspeed") { // 탄막 속도 가져오기
					MakeInstruction(0x0193, 0, 0, NULL);
				} else if(splitted[0] == "flad") { // 스택 부동 소수점 덧셈
					bool _res;
					uint32_t parsedValue;
					toLower(splitted[1]);
					_res = ValueParse(splitted[1], parsedValue);
					if(!_res) // 수치가 아니다!!
						throw DanmakuCompilerException(DanmakuCompilerException::InstructionArg);

					char *_data = new char[4];
					memcpy(_data, &parsedValue, 4);
					MakeInstruction(0x0012, 0, 4, _data);
				} else if(splitted[0] == "add") { // 정수 덧셈
					vector<string> argspl;
					bool _res;
					uint32_t parsedValue;

					toLower(splitted[1]);
					argspl = StrSplit(splitted[1], ",");
					_res = ValueParse(argspl[1], parsedValue);
					if(!_res) // 수치가 아니다!!
						throw DanmakuCompilerException(DanmakuCompilerException::InstructionArg);

					char *_data = new char[4];
					memcpy(_data, &parsedValue, 4);
					if(argspl[0] == "a") {
						MakeInstruction(0x0010, 0, 4, _data);
					} else if(argspl[0] == "b") {
						MakeInstruction(0x0011, 0, 4, _data);
					} else { // 대응하지 않는 피연산자
						delete _data;
						throw DanmakuCompilerException(DanmakuCompilerException::InstructionArg);
					}
				} else if(splitted[0] == "!entry") { // 엔트리 프로토타입
					vector<DanmakuCompilerEntry>::iterator i;
					DanmakuCompilerEntry tempE;
					string tempEntry(splitted[1]);
					for(i = EntryList.begin(); i != EntryList.end(); i++) {
						if(i->name == tempEntry) break;
					}
					if(i == EntryList.end()) {
						tempE.name = tempEntry;
						tempE.pos = -1;
						tempE.coded = false;
						EntryList.push_back(tempE);
					}
				}
				// TODO: 명령어 해석하는 것부터 좀 해 보자
			}
			// 코드 처리에서 빠져 나왔다
			// 이제 스크립트 구조대로 배열한다
			string NewFileName(path.c_str(), PathFindExtensionA(path.c_str()));
			NewFileName += ".spt";
			fstream Output;
			Output.exceptions(ios_base::badbit|ios_base::failbit);
			Output.open(NewFileName, ios_base::out|ios_base::binary);

			ScriptHeader SrcHd;
			BuildMagic(SrcHd.magic, "SCRT");
			SrcHd.EntryPos = 0x10;
			SrcHd.DataPos = 0x20;
			// 코드 엔트리 길이 계산
			uint32_t entrylen = 0, datalen = 0;
			{
				vector<DanmakuCompilerEntry>::iterator i;
				for(i = EntryList.begin(); i != EntryList.end(); i++) {
					entrylen += 5+(i->name).length()+1;
				}
			}
			SrcHd.DataPos += entrylen;
			/*
			{
				vector<DanmakuVariable>::iterator i;
				for(i = VarList.begin(); i != VarList.end(); i++) {
					datalen += i->len;
				}
			}
			*/
			SrcHd.CodePos = SrcHd.DataPos + 0x10 + varnowpos;
			Output.write(reinterpret_cast<char *>(&SrcHd), sizeof(SrcHd));

			SectionHeader EntryHeader, DataHeader, CodeHeader;
			memset(&EntryHeader, 0, sizeof(SectionHeader));
			memset(&DataHeader, 0, sizeof(SectionHeader));
			memset(&CodeHeader, 0, sizeof(SectionHeader));
			BuildMagic(EntryHeader.name, "ENTR");
			EntryHeader.length = entrylen;
			EntryHeader.position = SrcHd.EntryPos+0x10;
			Output.write(reinterpret_cast<char *>(&EntryHeader), 0x10);

			{
				vector<DanmakuCompilerEntry>::iterator i;
				for(i = EntryList.begin(); i != EntryList.end(); i++) {
					EntryData temp;
					temp.position = i->pos;
					temp.namelen = i->name.length()+1;
					Output.write(reinterpret_cast<char *>(&temp), 0x05);
					Output.write(i->name.c_str(), temp.namelen);
				}
			}

			BuildMagic(DataHeader.name, "DATA");
			DataHeader.length = varnowpos;
			DataHeader.position = SrcHd.DataPos+0x10;
			Output.write(reinterpret_cast<char *>(&DataHeader), 0x10);
			{
				vector<DanmakuVariable>::iterator i;
				for(i = VarList.begin(); i != VarList.end(); i++) {
					Output.write(i->DefValue, i->len);
				}
			}
			BuildMagic(CodeHeader.name, "CODE");
			CodeHeader.length = nowpos;
			CodeHeader.position = SrcHd.CodePos+0x10;
			Output.write(reinterpret_cast<char *>(&CodeHeader), 0x10);
			{
				vector<Instruction>::iterator i;
				for(i = InsList.begin(); i != InsList.end(); i++) {
					Output.write(reinterpret_cast<char *>(&(*i)), 6);
					Output.write(i->data, i->len);
					if(i->data) delete i->data;
				}
			}
			Output.close();
		} catch(DanmakuCompilerException e) {
			if(e.getCode() == DanmakuCompilerException::ok) return true;
			return false;
		} catch(ifstream::failure e) {
			return false;
		}
		return true;
	}

	bool DanmakuCompiler::ValueParse(string value, uint32_t &result)
	{
		bool isSucceed = true;
		int DataType;
		switch(value[value.length()-1])
		{
		case 'h': DataType = 0; break;
		case 'f': DataType = 1; break;
		default: DataType = 2; break;
		}

		result = 0;
		switch(DataType)
		{
		case 0:
			{
				for(uint32_t i = 0; i < value.length()-1; i++) {
					uint8_t tval;
					char tc;
					result<<=4;
					tc = value[i];
					if(tc >= '0' && tc <= '9') tval = tc-'0';
					else if(tc >= 'a' && tc <= 'f') tval = tc-'a'+10;
					else {
						isSucceed = false;
						return isSucceed;
					}
					result += tval;
				}
			}
			break;
		case 1:
			{
				uint32_t tresult = 0;
				float tdecimal = 0.0f, tmul = 1.0f;
				bool isDecimal = false, isNegative = false;
				uint32_t i = 0;
				if(value[0] == '-') isNegative = true, i++;
				else if(value[0] == '+') i++;
				for(; i < value.length()-1; i++) {
					uint8_t tval;
					char tc;
					tc = value[i];
					if(tc != '.') {
						if(isDecimal) tmul /= 10;
						else tresult *= 10;
					}
					if(tc >= '0' && tc <= '9') tval = tc-'0';
					else if(tc == '.') isDecimal = true;
					else {
						isSucceed = false;
						return isSucceed;
					}
					if(tc == '.') continue;
					if(isDecimal) tdecimal += tmul*tval;
					else tresult += tval;
				}
				int_float conv;
				conv._float = tresult+tdecimal * (isNegative?(-1):1);
				result = conv._int;
			}
			break;
		case 2:
			{
				bool isNegative = false;
				uint32_t i = 0;
				if(value[0] == '-') isNegative = true, i++;
				else if(value[0] == '+') i++;
				for(i = 0; i < value.length(); i++) {
					uint8_t tval;
					char tc;
					result*=10;
					tc = value[i];
					if(tc >= '0' && tc <= '9') tval = tc-'0';
					else {
						isSucceed = false;
						return isSucceed;
					}
					result += tval;
				}
				if(isNegative) result = -result;
			}
			break;
		}
		return isSucceed;
	}
	void DanmakuCompiler::MakeInstruction(uint16_t id, uint16_t prop, uint16_t len, char *data)
	{
		Instruction ins;
		ins.ID = id;
		ins.prop = prop;
		ins.len = len;
		ins.data = data;
		InsList.push_back(ins);
		nowpos += 6+len;
	}
	void DanmakuCompiler::BuildMagic(char *target, const char *magic)
	{
		for(int i = 0; i < 4; i++) {
			target[i] = magic[i];
		}
	}
}
